﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Threading;

namespace ProjectTW.Foliage
{
    [ExecuteInEditMode]
    public class ThreadManager : MonoBehaviour
    {
        private static ThreadManager s_Instance;
        private static Thread s_UnityThreadIdentifier;

        public static ThreadManager Instance
        {
            get
            {
                if (s_Instance == null)
                {
                    if (IsInUnityThread)
                    {
                        s_Instance = FindObjectOfType<ThreadManager>();

                        if (s_Instance == null)
                        {
                            GameObject go = new GameObject("Thread Manager");
                            s_Instance = go.AddComponent<ThreadManager>();

                            Debug.Log("Thread Manager Created!");
                        }
                    }
                    else
                    {
                        return null;
                    }
                }

                return s_Instance;
            }
        }

        public bool ThreadEnabled
        {
            get
            {
                return UNSettings.Instance.ThreadingEnabled;
            }
        }

        public int ThreadWorkersCount
        {
            get
            {
                return (int) UNSettings.Instance.ThreadingWorkersCount;
            }
        }

        public static float UpdateThreadItemsTime = 0.1f;

        private static List<IThreadTask> UnityThreadQueuedActions = new List<IThreadTask>();

        public static bool IsInUnityThread
        {
            get
            {
                return Thread.CurrentThread == s_UnityThreadIdentifier;
            }
        }

        private void OnEnable()
        {
            s_Instance = this;

            s_UnityThreadIdentifier = Thread.CurrentThread;

            ThreadPool.SetMinThreads(1, 1);
            ThreadPool.SetMaxThreads(ThreadWorkersCount, ThreadWorkersCount);
        }

        private void OnDisable()
        {
        }

        private void Awake()
        {
        }

        private void Update()
        {
            if (!Application.isPlaying)
            {
                return;
            }

            ThreadUpdate();
        }

        private void ThreadUpdate()
        {
            IThreadTask queuedData;

            for (int i = 0; i < UnityThreadQueuedActions.Count; i++)
            {
                queuedData = UnityThreadQueuedActions[i];

                if (queuedData != null)
                {
                    queuedData.Invoke();
                }
            }

            UnityThreadQueuedActions.Clear();
        }

        public void RunOnUnityThread(IThreadTask action)
        {
            if (action == null)
            {
                return;
            }

            if (IsInUnityThread)
            {
                action.Invoke();
            }
            else
            {
                UnityThreadQueuedActions.Add(action);
            }
        }

        public void RunOnThread(IThreadTask action)
        {
            if (ThreadEnabled)
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(OnThreadProcess), action);
            }
            else
            {
                RunOnUnityThread(action);
            }
        }

        /// <summary>
        /// 执行线程处理任务.
        /// </summary>
        /// <param name="processObject"></param>
        protected void OnThreadProcess(System.Object processObject)
        {
            IThreadTask task = processObject as IThreadTask;

            if (task != null)
            {
                try
                {
                    task.Invoke();
                }
                catch (UnityException ex)
                {
                    Debug.LogError("uNature Thread Manager : Error caught while running thread action : \n" + ex);
                }
            }
            else
            {
                Debug.LogError("uNature Thread Manager : Unrecognized thread process : " + processObject.ToString());
            }
        }

        public void DelayActionSeconds(IThreadTask task, float time)
        {
            StartCoroutine(DelayActionSecondsCoroutine(task, time));
        }

        public void DelayActionFrames(int frames, IThreadTask task)
        {
            StartCoroutine(DelayActionFrameCoroutine(frames, task));
        }

        private IEnumerator DelayActionSecondsCoroutine(IThreadTask task, float time)
        {
            yield return new WaitForSeconds(time);

            task.Invoke();
        }

        private IEnumerator DelayActionFrameCoroutine(int frames, IThreadTask task)
        {
            for (int i = 0; i < frames; i++)
            {
                yield return new WaitForEndOfFrame();
            }

            task.Invoke();
        }
    }

    public class ThreadTask : IThreadTask
    {
        private System.Action m_Action;
        private int m_Frame;

        public int CreationFrame
        {
            get
            {
                return m_Frame;
            }
        }

        public ThreadTask(System.Action action)
        {
            m_Action = action;

            if (ThreadManager.IsInUnityThread)
            {
                m_Frame = Time.frameCount;
            }
        }

        public void Invoke()
        {
            m_Action();
        }
    }

    public class ThreadTask<T> : IThreadTask
    {
        private System.Action<T> m_Action;
        private T m_Data;
        private int m_Frame;

        System.Action<ThreadTask<T>> onDone;

        public int CreationFrame
        {
            get
            {
                return m_Frame;
            }
        }

        public ThreadTask(System.Action<T> action, T data)
        {
            this.m_Action = action;
            this.m_Data = data;

            if (ThreadManager.IsInUnityThread)
            {
                m_Frame = Time.frameCount;
            }
        }

        public void Invoke()
        {
            m_Action(m_Data);
        }
    }

    public class ThreadTask<T, T1> : IThreadTask
    {
        private System.Action<T, T1> m_Action;
        private T m_Data1;
        private T1 m_Data2;
        private int Frame;

        public int CreationFrame
        {
            get
            {
                return Frame;
            }
        }

        public ThreadTask(System.Action<T, T1> action, T data1, T1 data2)
        {
            m_Action = action;
            m_Data1 = data1;
            m_Data2 = data2;

            if (ThreadManager.IsInUnityThread)
            {
                Frame = Time.frameCount;
            }
        }

        public void Invoke()
        {
            m_Action(m_Data1, m_Data2);
        }
    }

    public class ThreadTask<T, T1, T2> : IThreadTask
    {
        private System.Action<T, T1, T2> m_Action;
        private T m_Data1;
        private T1 m_Data2;
        private T2 m_Data3;
        private int m_Frame;

        public int CreationFrame
        {
            get
            {
                return m_Frame;
            }
        }

        public ThreadTask(System.Action<T, T1, T2> action, T data1, T1 data2, T2 data3)
        {
            this.m_Action = action;
            this.m_Data1 = data1;
            this.m_Data2 = data2;
            this.m_Data3 = data3;

            if (ThreadManager.IsInUnityThread)
            {
                m_Frame = Time.frameCount;
            }
        }

        public void Invoke()
        {
            m_Action(m_Data1, m_Data2, m_Data3);
        }
    }

    public class ThreadTask<T, T1, T2, T3> : IThreadTask
    {
        private System.Action<T, T1, T2, T3> m_Action;

        private T m_Data1;
        private T1 m_Data2;
        private T2 m_Data3;
        private T3 m_Data4;
        private int m_Frame;

        public int CreationFrame
        {
            get
            {
                return m_Frame;
            }
        }

        public ThreadTask(System.Action<T, T1, T2, T3> action, T data1, T1 data2, T2 data3, T3 data4)
        {
            m_Action = action;
            m_Data1 = data1;
            m_Data2 = data2;
            m_Data3 = data3;
            m_Data4 = data4;

            if (ThreadManager.IsInUnityThread)
            {
                m_Frame = Time.frameCount;
            }
        }

        public void Invoke()
        {
            m_Action(m_Data1, m_Data2, m_Data3, m_Data4);
        }
    }

    public interface IThreadTask
    {
        void Invoke();

        int CreationFrame
        {
            get;
        }
    }
}
